// Copyright 2001-2006, FreeHEP.
package org.freehep.util.io;

import java.io.IOException;
import java.io.InputStream;

/**
 * Class to read Tagged blocks from a Stream. The tagged blocks (Tags) contain a
 * tagID and a Length, so that known and unknown tags can be read and written
 * (using the TaggedOutputStream). The stream also allows to read Actions, which
 * again come with a actionCode and a length.
 * 
 * A set of recognized Tags and Actions can be added to this stream. A concrete
 * implementation of this stream should decode/read the TagHeader. All Concrete
 * tags should be inherited from the Tag class and implement their read methods.
 * 
 * @author Mark Donszelmann
 * @author Charles Loomis
 * @version $Id: TaggedInputStream.java,v 1.3 2008/05/04 12:22:14 murkle Exp $
 */
public abstract class TaggedInputStream extends ByteCountInputStream {

    /**
     * Set of tags that can be used by this Stream
     */
    protected TagSet tagSet;

    /**
     * Set of actions that can be used by this Stream
     */
    protected ActionSet actionSet;

    /**
     * Currently read tagHeader, valid during readTag call.
     */
    private TagHeader tagHeader;
    
    /**
     * Creates a Tagged Input Stream
     * 
     * @param in stream to read from
     * @param tagSet available tag set
     * @param actionSet available action set
     */
    public TaggedInputStream(InputStream in, TagSet tagSet, ActionSet actionSet) {
        this(in, tagSet, actionSet, false);
    }

    /**
     * Creates a Tagged Input Stream
     * 
     * @param in stream to read from
     * @param tagSet available tag set
     * @param actionSet available action set
     * @param littleEndian true if stream is little endian
     */
    public TaggedInputStream(InputStream in, TagSet tagSet,
            ActionSet actionSet, boolean littleEndian) {
        super(in, littleEndian, 8);

        this.tagSet = tagSet;
        this.actionSet = actionSet;
    }

    /**
     * Add tag to tagset
     * 
     * @param tag new tag
     */
    public void addTag(Tag tag) {
        tagSet.addTag(tag);
    }

    /**
     * Decodes and returns the TagHeader, which includes a TagID and a length.
     * 
     * @return Decoded TagHeader
     * @throws IOException if read fails
     */
    protected abstract TagHeader readTagHeader() throws IOException;

    /**
     * Read a tag.
     * @return read tag
     * @throws IOException if read fails
     */
    public Tag readTag() throws IOException {

        tagHeader = readTagHeader();
        if (tagHeader == null)
            return null;

        int size = (int) tagHeader.getLength();

        // Look up the proper tag.
        Tag tag = tagSet.get(tagHeader.getTag());

        // set max tag length and read tag
        pushBuffer(size);
        tag = tag.read(tagHeader.getTag(), this, size);
        byte[] rest = popBuffer();

        // read non-read part of tag
        if (rest != null) {
            throw new IncompleteTagException(tag, rest);
        }
        return tag;
    }

    /**
     * Returns the currently valid TagHeader. Can be called durring the tag.read() method.
     */
    public TagHeader getTagHeader() {
        return tagHeader;
    }
    
    /**
     * Add action to action set.
     * 
     * @param action new action
     */
    public void addAction(Action action) {
        actionSet.addAction(action);
    }

    /**
     * Decodes and returns the ActionHeader, which includes an actionCode and a
     * length.
     * 
     * @return decoded ActionHeader 
     * @throws IOException if read fails
     */
    protected abstract ActionHeader readActionHeader() throws IOException;

    /**
     * Reads action.
     * 
     * @return read action
     * @throws IOException if read fails
     */
    public Action readAction() throws IOException {

        ActionHeader header = readActionHeader();
        if (header == null)
            return null;

        int size = (int) header.getLength();

        // Look up the proper action.
        Action action = actionSet.get(header.getAction());

        pushBuffer(size);
        action = action.read(header.getAction(), this, size);
        byte[] rest = popBuffer();

        if (rest != null) {
            throw new IncompleteActionException(action, rest);
        }
        return action;
    }

}
